Skip to content

feat(api): add ServicePlugin.getPreferredColor() with parent chain inheritance#22

Merged
fdaugan merged 1 commit into
ligoj:masterfrom
Terracosmos:norman/feat-preferred-color
Jun 11, 2026
Merged

feat(api): add ServicePlugin.getPreferredColor() with parent chain inheritance#22
fdaugan merged 1 commit into
ligoj:masterfrom
Terracosmos:norman/feat-preferred-color

Conversation

@Terracosmos

Copy link
Copy Markdown
Contributor

Context

The Ligoj dashboard now displays each tool as a card with a preferred color (see ligoj/plugin-ui#15). The PR that implemented the cards (ligoj/plugin-ui#27) used a frontend-only fallback (PNG canvas extraction) because no API existed to let a plugin declare its color.

This PR introduces the data model evolution mentioned in the original issue ("Each card has a preferred color. Either guessed from the SVG icon of the tool, either provided (need data model evolution) by the plugin"). It is the backend foundation for the cleaner "plugin-provided color" path.

Changes

  • ServicePlugin#getPreferredColor() default method returning null — opt-in, zero breaking change
  • NodeVo#preferredColor field exposed in the REST payload
  • NodeHelper#applyPlugin(vo, entity, locator) invoked from the three locator-aware variants (toVo, toVoLight, toVoParameter)
  • NodeHelper#resolvePreferredColor(entity, locator) walks the parent chain via Node#getRefined() so a color declared at the service level (e.g. service:id) is inherited by its tools (e.g. service:id:ldap); leaf takes precedence over ancestors
  • Updated ServicePluginTest (default null check)
  • New NodeHelperTest: 6 tests covering the three variants, null cases, parent inheritance, child override priority

Design rationale

Why on ServicePlugin (not ToolPlugin or FeaturePlugin)

The color is static branding metadata, semantically closer to getName()/getVendor() on FeaturePlugin than to runtime methods like checkStatus(...) on ToolPlugin. Placing it on ToolPlugin would have excluded service-only nodes (service:id, service:prov, ...) from declaring a color. Placing it on FeaturePlugin would be more orthogonal but lives in the ligoj-bootstrap repository, requiring an additional coordinated release. ServicePlugin is the pragmatic middle ground that covers both service and tool plugins without crossing repository boundaries.

Why factor applyPlugin across the three toVo* variants

NodeHelper exposes three locator-aware variants consumed by different REST endpoints (findById, findAll, parameter-aware lookups). A factored helper guarantees the color is filled regardless of which endpoint the consumer hits, with no logic duplication. Variants without a locator (toVo(entity), ...) are intentionally left untouched: no locator means no plugin resolution, which is consistent with their current contract.

Why walk up the parent chain manually

locator.getResource(id, ServicePlugin.class) already walks the parent chain to find a plugin, but returns the leaf's own value (not a composed inheritance). For colors specifically, that means a service-level color declared on IdentityResource (service:id) would not bubble down to its tool nodes (service:id:ldap) without an explicit walk-up. The explicit walk-up keeps the API ergonomic: a single override on the parent service covers all its tools by default, while individual tools can still override locally (leaf-first priority). This was validated end-to-end at runtime.

Why a null default and how Jackson handles it

A null default keeps backward compatibility intact: every existing plugin remains valid without modification. Jackson's default configuration on Ligoj omits null fields from the JSON payload (verified at runtime via GET /rest/node/service:id), so consumers that don't yet know about preferredColor see no change in the API surface. The field appears in the payload only when a plugin in the chain declares a non-null value.

Backward compatibility

  • Default null return on ServicePlugin#getPreferredColor() → no existing plugin needs to change
  • Walk-up resolves a color only when at least one plugin in the chain declares one
  • Jackson omits null fields → no change in the existing JSON contract
  • No version bump needed (work fits within the already-open 4.2.3-SNAPSHOT)

Tests

mvn install: 487 tests passed, 0 failed, 0 skipped (+6 vs. master).

End-to-end runtime verified locally:

  1. Override on service:id (Identity) returns the declared color via GET /rest/node/service:id
  2. Tool service:id:ldap inherits the parent color via GET /rest/node/service:id:ldap (walk-up)

Follow-up PRs

  • ligoj/ligoj (host): NodeIcon.vue reads node.preferredColor and uses it as the card accent color
  • Tool plugins: override getPreferredColor() with the brand color (e.g. #0052CC for Jira, #FF9900 for AWS)

@fdaugan fdaugan merged commit 790cbf4 into ligoj:master Jun 11, 2026
2 checks passed
@fdaugan

fdaugan commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

preferredColor has been renamed in uiColor by 4773a54

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants